﻿
namespace Contracts.ClientTools
{
    #region using

    using System;
    using System.Collections.Concurrent;
    using System.Configuration;
    using System.Diagnostics;
    using System.Linq;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using Types.Constants;

    #endregion

    internal static class Msg
    {
        public const string BindingNotSupported = "Binding is not supported.";
        public const string Timeout = "TimeOut exception occurred when completing call. Interface:{0} | Method: {1} | StackTrace:{2}";
    }

    internal static class Prm
    {
        public const string EndPoint = "endPoint";
        public const string IsProd = "IsProd";
        public const string BindingName = "HKBinding";
        public const string CustomBindingName = "WcfCustomBinding";
    }

    internal static class ProxyManager<IType>
    {
        internal static ConcurrentDictionary<string, Lazy<ChannelFactory<IType>>> proxies = new ConcurrentDictionary<string, Lazy<ChannelFactory<IType>>>();

        internal static IType GetProxy(string key)
        {
            return proxies.GetOrAdd(key, m => new Lazy<ChannelFactory<IType>>(() => ProxyHelper<IType>.GetServiceProxy())).Value.CreateChannel();
        }

        internal static IType GetProxy(string key, string endPointType)
        {
            return proxies.GetOrAdd(key, m => new Lazy<ChannelFactory<IType>>(() => ProxyHelper<IType>.GetServiceProxy(endPointType))).Value.CreateChannel();
        }

        internal static bool RemoveProxy(string key)
        {
            Lazy<ChannelFactory<IType>> proxy;
            return proxies.TryRemove(key, out proxy);
        }
    }

    internal static class ProxyHelper<TInterfaceType>
    {
        private static string endPoint = "net.tcp://localhost/WcfServices/";
        private static string mEndPointScheme;
        private static bool mIsProd = false;

        static ProxyHelper()
        {
            AppSettingsReader appReader = new AppSettingsReader();
            if (ConfigurationManager.AppSettings.AllKeys.Contains(Prm.EndPoint))
                endPoint = appReader.GetValue(Prm.EndPoint, typeof(string)).ToString();

            Uri epUri = new Uri(endPoint);
            mEndPointScheme = epUri.Scheme;

            if (ConfigurationManager.AppSettings.AllKeys.Contains(Prm.IsProd))
            {
                mIsProd = Convert.ToBoolean(appReader.GetValue(Prm.IsProd, typeof(bool)));
            }
        }

        internal static ChannelFactory<TInterfaceType> GetServiceProxy()
        {
            Binding bnd;
            switch (mEndPointScheme)
            {
                case "net.tcp":
                    bnd = new CustomBinding(Prm.CustomBindingName);
                    //bnd = new NetTcpBinding(Prm.BindingName);
                    break;
                case "http":
                    bnd = new BasicHttpBinding(Prm.BindingName);
                    break;
                case "net.pipe":
                    bnd = new NetNamedPipeBinding(Prm.BindingName);
                    break;
                default:
                    throw new NotSupportedException(Msg.BindingNotSupported);
            }

            EndpointAddress epAdres;

            //ChannelFactory<TInterfaceType> channelFact;

            if (!mIsProd)
            {
                epAdres = new EndpointAddress(endPoint + typeof(TInterfaceType).Name);
            }
            else
            {
                epAdres = new EndpointAddress(endPoint + typeof(TInterfaceType).Name + ".svc");
            }

            ServiceEndpoint sepp = new ServiceEndpoint(ContractDescription.GetContract(typeof(TInterfaceType)), bnd, epAdres);

            sepp.Behaviors.Add(new CustomClientMessageInspectorBehavior());

            ///OLCAYU(13.05.2011): İstemci tarafından büyük verilerin gönderilebilmesini sağlamak amacıyle aşağıdaki kısım eklendi
            ///Bu kısımda config dosyası içerisinde behaviour eklenemediği için kod ile bu işlem yapılmaya çalışıldı
            foreach (OperationDescription op in sepp.Contract.Operations)
            {
                DataContractSerializerOperationBehavior dataContractBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>() as DataContractSerializerOperationBehavior;
                if (dataContractBehavior != null)
                {
                    dataContractBehavior.MaxItemsInObjectGraph = 2147483647;
                }
            }
            return new ChannelFactory<TInterfaceType>(sepp);
        }

        internal static ChannelFactory<TInterfaceType> GetServiceProxy(string endPointType)
        {
            //Binding icin endpoint Uri scheme'e gore karar veriliyor. 

            AppSettingsReader appReader = new AppSettingsReader();

            var serviceEndpoint = string.Format("endPoint{0}", endPointType);
            if (ConfigurationManager.AppSettings.AllKeys.Contains(serviceEndpoint))
                endPoint = appReader.GetValue(serviceEndpoint, typeof(string)).ToString();

            Uri epUri = new Uri(endPoint);
            mEndPointScheme = epUri.Scheme;

            var bindingType = string.Format("binding{0}", endPointType);
            var bindingConfigurationType = string.Format("bindingConfiguration{0}", endPointType);

            Binding bnd;

            if (ConfigurationManager.AppSettings.AllKeys.Contains(bindingType))
            {
                string bindingTypeName = appReader.GetValue(bindingType, typeof(string)).ToString();
                string bindingConfigurationTypeName = null;
                if (ConfigurationManager.AppSettings.AllKeys.Contains(bindingConfigurationType))
                    bindingConfigurationTypeName = appReader.GetValue(bindingConfigurationType, typeof(string)).ToString();
                switch (bindingTypeName)
                {
                    case "netNamedPipeBinding":
                        if (bindingConfigurationTypeName == null)
                            bnd = new NetNamedPipeBinding();
                        else
                            bnd = new NetNamedPipeBinding(bindingConfigurationTypeName);
                        break;
                    case "netTcpBinding":
                        if (bindingConfigurationTypeName == null)
                            bnd = new NetTcpBinding();
                        else
                            bnd = new NetTcpBinding(bindingConfigurationTypeName);
                        break;

                    case "basicHttpBinding":
                        if (bindingConfigurationTypeName == null)
                            bnd = new BasicHttpBinding();
                        else
                            bnd = new BasicHttpBinding(bindingConfigurationTypeName);
                        break;

                    default:
                        throw new NotSupportedException(Msg.BindingNotSupported);
                }
            }
            else
            {
                switch (mEndPointScheme)
                {
                    case "net.tcp":
                        bnd = new CustomBinding(Prm.CustomBindingName);
                        //bnd = new NetTcpBinding(Prm.BindingName);
                        break;
                    case "http":
                        bnd = new BasicHttpBinding(Prm.BindingName);
                        break;
                    case "net.pipe":
                        bnd = new NetNamedPipeBinding(Prm.BindingName);
                        break;
                    default:
                        throw new NotSupportedException(Msg.BindingNotSupported);
                }
            }

            EndpointAddress epAdres;

            //ChannelFactory<TInterfaceType> channelFact;

            if (!mIsProd)
            {
                epAdres = new EndpointAddress(endPoint + typeof(TInterfaceType).Name);
            }
            else
            {
                epAdres = new EndpointAddress(endPoint + typeof(TInterfaceType).Name + ".svc");
            }

            ServiceEndpoint sepp = new ServiceEndpoint(ContractDescription.GetContract(typeof(TInterfaceType)), bnd, epAdres);
            sepp.Behaviors.Add(new CustomClientMessageInspectorBehavior());

            ///OLCAYU(13.05.2011): İstemci tarafından büyük verilerin gönderilebilmesini sağlamak amacıyle aşağıdaki kısım eklendi
            ///Bu kısımda config dosyası içerisinde behaviour eklenemediği için kod ile bu işlem yapılmaya çalışıldı
            foreach (OperationDescription op in sepp.Contract.Operations)
            {
                DataContractSerializerOperationBehavior dataContractBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>() as DataContractSerializerOperationBehavior;
                if (dataContractBehavior != null)
                {
                    dataContractBehavior.MaxItemsInObjectGraph = 2147483647;
                }
            }
            return new ChannelFactory<TInterfaceType>(sepp);
            //channelFact = new ChannelFactory<TInterfaceType>(sepp);

            //// Attach client behavior....
            //TInterfaceType proxy = channelFact.CreateChannel();
            //return proxy;
        }
    }

    public delegate TResultType CallServiceDelegate<TInterfaceType, TResultType>(TInterfaceType proxy);
    public delegate void CallServiceDelegate<TInterfaceType>(TInterfaceType proxy);

    public static class ServiceProxyCreator<TInterfaceType>
    {
        public static void CallService(CallServiceDelegate<TInterfaceType> pMethod)
        {
            TInterfaceType proxy = ProxyManager<TInterfaceType>.GetProxy(typeof(TInterfaceType).Name);
            CallServiceResult(pMethod, proxy);
        }

        public static void CallService(CallServiceDelegate<TInterfaceType> pMethod, string endPointType)
        {
            TInterfaceType proxy = ProxyManager<TInterfaceType>.GetProxy(typeof(TInterfaceType).Name, endPointType);
            CallServiceResult(pMethod, proxy);
        }

        private static void CallServiceResult(CallServiceDelegate<TInterfaceType> pMethod, TInterfaceType proxy)
        {
            bool mSuccess = false;
            IClientChannel castProxy = proxy as IClientChannel;

            try
            {
                pMethod(proxy);
                castProxy.Close();
                mSuccess = true;
            }
            catch (System.TimeoutException ex)
            {
                //no need to rethrow,
                System.Diagnostics.EventLog.WriteEntry(CommonConstants.EventLogSource.Application, string.Format("TimeOut exception occurred when completing call. Interface:{0} - StackTrace:{1}" + ex.StackTrace, typeof(TInterfaceType).Name, pMethod.ToString()), System.Diagnostics.EventLogEntryType.Warning, 7070);
            }
            finally
            {
                if (!mSuccess)
                {
                    //TODO:remove this when stable                    
                    //System.Diagnostics.EventLog.WriteEntry(CommonConstants.EventLogSource.Application, string.Format("Failed to call interface-method as {0}-{1}", typeof(TInterfaceType).Name, pMethod.ToString()), System.Diagnostics.EventLogEntryType.Warning, 7070);

                    if (castProxy != null)
                        castProxy.Abort();
                }
                if (castProxy != null)
                    castProxy = null;
            }
        }
    }

    public static class ServiceProxyCreator<TInterfaceType, TResultType>
    {
        public static TResultType CallService(CallServiceDelegate<TInterfaceType, TResultType> pMethod)
        {
            TResultType returnObject;

            TInterfaceType proxy = ProxyManager<TInterfaceType>.GetProxy(typeof(TInterfaceType).Name);
            return CallServiceResult(pMethod, proxy, out returnObject);
        }

        public static TResultType CallService(CallServiceDelegate<TInterfaceType, TResultType> pMethod, string endPointType)
        {
            TResultType returnObject;
            TInterfaceType proxy = ProxyManager<TInterfaceType>.GetProxy(typeof(TInterfaceType).Name, endPointType);
            return CallServiceResult(pMethod, proxy, out returnObject);
        }

        private static TResultType CallServiceResult(CallServiceDelegate<TInterfaceType, TResultType> pMethod, TInterfaceType proxy, out TResultType returnObject)
        {
            bool mSuccess = false;
            IClientChannel castProxy = proxy as IClientChannel;
            returnObject = default(TResultType);
            //string tName = typeof(TInterfaceType).Name;
            try
            {
                returnObject = pMethod(proxy);
                castProxy.Close();
                mSuccess = true;
                //return returnObject;
            }
            catch (System.TimeoutException exc)
            {
                //no need to rethrow

                string excMsg = string.Format(Msg.Timeout, typeof(TInterfaceType).Name, pMethod.ToString(), exc.StackTrace);

                EventLog.WriteEntry(CommonConstants.EventLogSource.Application, excMsg, EventLogEntryType.Warning, 7070);
            }
            finally
            {
                if (!mSuccess)
                {
                    //TODO:remove this when stable                    
                    //System.Diagnostics.EventLog.WriteEntry(CommonConstants.EventLogSource.Application, string.Format("Failed to complete calling interface-method as {0}-{1}", typeof(TInterfaceType).Name, pMethod.ToString()), System.Diagnostics.EventLogEntryType.Warning, 7070);

                    if (castProxy != null)
                        castProxy.Abort();
                }
                if (castProxy != null)
                    castProxy = null;
            }
            return returnObject;
        }
    }
}